package util

import (
	"bytes"
	"compress/zlib"
	"crypto/md5"
	"encoding/json"
	"flag"
	"fmt"
	"gitee.com/kingzyt/common/bufferPool"
	bytes2 "gitee.com/kingzyt/common/bytes"
	"gitee.com/kingzyt/common/log"
	"gitee.com/kingzyt/common/marshal"
	"gitee.com/kingzyt/common/objPool"
	"io"
	"net"
	"os"
	"os/signal"
	"reflect"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"sync/atomic"
	"syscall"
	"time"
)

func BytesClone(src []byte) []byte {
	buf := make([]byte, len(src))
	copy(buf, src)
	return buf
}

func getPanicTrace() []string {
	infobuf := bufferPool.GlobalBufPull(1500)
	defer bufferPool.GlobalBufPush(infobuf)

	n := runtime.Stack(infobuf.Value, false)

	return strings.SplitN(string(infobuf.Value[:n]), "\n", 6)
}

func PrintPanicTrace() {
	log.Error("", "\n%s", getPanicTrace()[5])
}
func PrintPanicTraceFmt() {
	fmt.Printf("\n%s\n", getPanicTrace()[5])
}

func GetSysQuitSign() <-chan os.Signal {
	c := make(chan os.Signal, 1)
	// signal.Notify(c, os.Kill, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGINT)
	signal.Notify(c, os.Kill, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGKILL)
	return c
}

type ESCCancel string

func (e ESCCancel) Error() string {
	// just return err str, no string combine, reuse string stored
	return "ESC:" + string(e)
}

func StrRemoveWhitespaceChars(str string) string {
	str = strings.Replace(str, "\n", "", -1)
	str = strings.Replace(str, "\r", "", -1)
	str = strings.TrimSpace(str)
	return str
}

var Net_local_IPs []string

func FetchMachineLocalPs() ([]string, bool) {
	if Net_local_IPs != nil {
		return Net_local_IPs, true
	}

	info, err := net.InterfaceAddrs()
	if err != nil {
		log.Error("", "get net interface addrs err:%s", err)
		return nil, false
	}
	for _, addr := range info {
		Net_local_IPs = append(Net_local_IPs, strings.Split(addr.String(), "/")[0])
	}

	return Net_local_IPs, true
}

func GetIntranetIP() string {
	localIPs, ok := FetchMachineLocalPs()
	if !ok {
		return ""
	}

	for i := 0; i < len(localIPs); i++ {
		ip := localIPs[i]
		if ip == "127.0.0.1" {
			continue
		}
		return ip
	}
	return ""
}

func GetAddrIP(addr string) string {
	addr = strings.Replace(addr, " ", "", -1)
	host, _, err := net.SplitHostPort(addr)
	if err != nil {
		log.Error("", "GetAddrIP err:%s", err)
		return addr
	}
	return host
}
func GetAddrPort(addr string) int32 {
	addr = strings.Replace(addr, " ", "", -1)
	_, portStr, err := net.SplitHostPort(addr)
	if err != nil {
		log.Error("", "GetAddrPort err:%s", err)
		return -1
	}
	port, err := strconv.Atoi(portStr)
	if err != nil {
		return -1
	}
	return int32(port)
}

// S需要外部网络地址的时候，需要先判定是否同处于一个机器上，是的话，转成本地loop地址，最大限度加速网络传输
func MergeSameMachineServerIPToLocal(addr string) string {
	addr = strings.Replace(addr, " ", "", -1)
	localIPs, ok := FetchMachineLocalPs()
	if !ok {
		return addr
	}

	host, port, err := net.SplitHostPort(addr)
	if err != nil {
		log.Error("", "MergeSameMachineServerIPToLocal err:%s", err)
		return addr
	}

	if host == "127.0.0.1" || host == "localhost" {
		return addr
	}

	for _, localIP := range localIPs {
		if localIP == host {
			return "127.0.0.1:" + port
		}
	}

	return addr
}

func IsLocalIP(ip string) bool {
	ip = strings.Replace(ip, " ", "", -1)
	localIPs, ok := FetchMachineLocalPs()
	if !ok {
		return false
	}
	for _, localIP := range localIPs {
		if localIP == ip {
			return true
		}
	}
	return false
}

func GetStrArg(module string, argName string, arg interface{}) (str string) {
	str, ok := arg.(string)
	if !ok {
		panic(fmt.Errorf("%s, %s should be string type, now: %v", module, argName, arg))
	}
	return
}

func RedisTestLog(info string) string {
	return "redis.log(redis.LOG_NOTICE, " + info + ")"
}

func DeferRecoverPanic(module string) {
	if r := recover(); r != nil {
		log.Error(module, "panic happen:%s", r)
		PrintPanicTrace()
	}
}

func LoadJson(module string, path string, v interface{}) bool {
	fileBytes, ok := ReadFile(module, path)
	if !ok {
		log.Error(module, path+" read err")
		return false
	}

	err := json.Unmarshal(fileBytes, v)
	if err != nil {
		log.Error(module, path+", json unmarshal err:%s", err)
		return false
	} else {
		log.Info(module, log.I, path+" read OK")
		return true
	}
}

func IntListMultiDel(list []int32, target int32) (rlt []int32, elemDeleted bool) {
	cursor := 0
	for i := 0; i < len(list); i++ {
		if list[i] == target {
			continue
		} else {
			if i > cursor {
				list[cursor] = list[i]
			}
			cursor++
		}
	}

	if cursor < len(list) {
		return list[:cursor], true
	} else {
		return list, false
	}
}
func Int64ListMultiDel(list []int64, target int64) (rlt []int64, elemDeleted bool) {
	cursor := 0
	for i := 0; i < len(list); i++ {
		if list[i] == target {
			continue
		} else {
			if i > cursor {
				list[cursor] = list[i]
			}
			cursor++
		}
	}

	if cursor < len(list) {
		return list[:cursor], true
	} else {
		return list, false
	}
}

func SliceMultiDelByReflect(slice interface{}, doDel func(idx int, elem interface{}) bool) (rlt interface{}, elemDeleted bool) {
	list := reflect.ValueOf(slice)
	if list.Kind() != reflect.Slice {
		panic(fmt.Sprintf("SliceMultiDelByReflect only operate on slice, cur kind is %s", list.Kind()))
	}

	listLen := list.Len()

	cursor := 0
	for i := 0; i < listLen; i++ {
		if doDel(i, list.Index(i).Interface()) {
			continue
		} else {
			if i > cursor {
				list.Index(cursor).Set(list.Index(i))
			}
			cursor++
		}
	}

	if cursor < listLen {
		for i := cursor; i < listLen; i++ {
			v := list.Index(i)
			v.Set(reflect.Zero(v.Type()))
		}

		return list.Slice(0, cursor).Interface(), true
	} else {
		return slice, false
	}
}

// can delete multiple elements
func SliceDeleteElem(slice *[]int32, doDel func(elem int32) bool) {
	cursor := 0
	slice_ := *slice
	slen := len(slice_)
	for i := 0; i < slen; i++ {
		if doDel(slice_[i]) {
			continue
		} else {
			if i > cursor {
				slice_[cursor] = slice_[i]
			}
			cursor++
		}
	}

	*slice = slice_[:cursor]
}

func WaitProc(timeout time.Duration, updateInterval time.Duration, proc func() (end bool), timeoutProc func()) {
	if proc == nil {
		panic("WaitProc proc is nil")
	}

	timeouter := time.NewTimer(timeout)
	defer timeouter.Stop()
	timer := time.NewTicker(updateInterval)
	defer timer.Stop()
End:
	for {
		select {
		case <-timeouter.C:
			if timeoutProc != nil {
				timeoutProc()
			}
			return
		case <-timer.C:
			if proc() {
				break End
			}
		}
	}
}

func WaitSignalProc(timeout time.Duration, signalChan <-chan byte, proc func(val byte), timeoutProc func()) {
	if proc == nil {
		panic("WaitProc proc is nil")
	}

	timeouter := time.NewTimer(timeout)
	defer timeouter.Stop()
	select {
	case <-timeouter.C:
		if timeoutProc != nil {
			timeoutProc()
		}
		return
	case val := <-signalChan:
		proc(val)
		return
	}
}

func UpdateProcWithDt(updateInterval time.Duration, directlyDoAndWait bool, proc func(dtNs int64) (ok bool)) {
	if proc == nil {
		panic("UpdateProc proc is nil")
	}

	// first directly do once
	if directlyDoAndWait && !proc(0) {
		return
	}

	timer := time.NewTicker(updateInterval)
	defer timer.Stop()
	lastT := time.Now().UnixNano()
	for {
		now_ := <-timer.C
		now := now_.UnixNano()
		dtNs := now - lastT
		lastT = now

		if !proc(dtNs) {
			return
		}
	}
}

func UpdateProc(updateInterval time.Duration, directlyDoAndWait bool, proc func() (ok bool)) {
	if proc == nil {
		panic("UpdateProc proc is nil")
	}

	// first directly do once
	if directlyDoAndWait && !proc() {
		return
	}

	timer := time.NewTicker(updateInterval)
	defer timer.Stop()
	for {
		<-timer.C

		if !proc() {
			return
		}
	}
}

func UpdateProcWithNow(updateInterval time.Duration, directlyDoAndWait bool, proc func(now time.Time) (ok bool)) {
	if proc == nil {
		panic("UpdateProc proc is nil")
	}

	// first directly do once
	if directlyDoAndWait && !proc(time.Now()) {
		return
	}

	timer := time.NewTicker(updateInterval)
	defer timer.Stop()
	for {
		now := <-timer.C

		if !proc(now) {
			return
		}
	}
}

func GetFlagInt(name string) int {
	rlt, err := strconv.Atoi(flag.Lookup(name).Value.String())
	if err != nil {
		panic(name + " is not number")
	}
	return rlt
}
func GetFlagFloat(name string) float32 {
	rlt, err := strconv.ParseFloat(flag.Lookup(name).Value.String(), 32)
	if err != nil {
		panic(name + " is not number")
	}
	return float32(rlt)
}
func GetFlagStr(name string) string {
	return flag.Lookup(name).Value.String()
}

type AtomicInt32 int32

func (self *AtomicInt32) Get() int32 {
	return atomic.LoadInt32((*int32)(self))
}
func (self *AtomicInt32) Set(val int32) {
	atomic.StoreInt32((*int32)(self), val)
}
func (self *AtomicInt32) Add(val int32) int32 {
	return atomic.AddInt32((*int32)(self), val)
}
func (self *AtomicInt32) CompareAndSwap(oldV int32, newV int32) bool {
	return atomic.CompareAndSwapInt32((*int32)(self), oldV, newV)
}

type AtomicUint32 uint32

func (self *AtomicUint32) Get() uint32 {
	return atomic.LoadUint32((*uint32)(self))
}
func (self *AtomicUint32) Set(val uint32) {
	atomic.StoreUint32((*uint32)(self), val)
}
func (self *AtomicUint32) Add(val uint32) uint32 {
	return atomic.AddUint32((*uint32)(self), val)
}
func (self *AtomicUint32) CompareAndSwap(oldV uint32, newV uint32) bool {
	return atomic.CompareAndSwapUint32((*uint32)(self), oldV, newV)
}

type AtomicInt64 int64

func (self *AtomicInt64) Get() int64 {
	return atomic.LoadInt64((*int64)(self))
}
func (self *AtomicInt64) Set(val int64) {
	atomic.StoreInt64((*int64)(self), val)
}
func (self *AtomicInt64) Add(val int64) int64 {
	return atomic.AddInt64((*int64)(self), val)
}
func (self *AtomicInt64) CompareAndSwap(oldV int64, newV int64) bool {
	return atomic.CompareAndSwapInt64((*int64)(self), oldV, newV)
}

type AtomicUint64 uint64

func (self *AtomicUint64) Get() uint64 {
	return atomic.LoadUint64((*uint64)(self))
}
func (self *AtomicUint64) Set(val uint64) {
	atomic.StoreUint64((*uint64)(self), val)
}
func (self *AtomicUint64) Add(val uint64) uint64 {
	return atomic.AddUint64((*uint64)(self), val)
}
func (self *AtomicUint64) CompareAndSwap(oldV uint64, newV uint64) bool {
	return atomic.CompareAndSwapUint64((*uint64)(self), oldV, newV)
}

func StartBGGoroutineStackCollect(name string) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGUSR2)

	for {
		<-c

		buf := make([]byte, 20000)
		for {
			n := runtime.Stack(buf, true)
			if n < len(buf) {
				buf = buf[:n]
				break
			}
			buf = make([]byte, 2*len(buf))
		}

		filepath := fmt.Sprintf("%s/goStack_%s_%s.txt", GetExecDir(), name, time.Now().Format("2006-01-02T15_04_05"))
		WriteBinFile(filepath, buf, os.O_TRUNC|os.O_CREATE|os.O_WRONLY)
		buf = nil
	}
}

/////////////////////////
var g_marshalManualRecyclerPool = objPool.NewObjPool("g_marshalManualRecyclerPool", 5, 5, func() interface{} {
	return &MarshalManualRecycler{recycleBufContainer: make([]*bytes2.Element, 32)} // prealloc
})

type MarshalManualRecycler struct {
	recycleBufContainer []*bytes2.Element
}

func NewMarshalManualRecycler() *MarshalManualRecycler {
	r := g_marshalManualRecyclerPool.Pull().(*MarshalManualRecycler)
	r.recycleBufContainer = r.recycleBufContainer[:0]
	return r
}

func (self *MarshalManualRecycler) Marshal(module string, data marshal.MarshalEntity, typeStr string) (marshaledBuf []byte, ok bool) {
	recycleBuf, marshaledBuf, ok := marshal.MarshalManualRecycle(module, data, typeStr)
	if !ok {
		return
	}
	if recycleBuf != nil {
		self.recycleBufContainer = append(self.recycleBufContainer, recycleBuf)
	}
	return
}

func (self *MarshalManualRecycler) Release() {
	for i := 0; i < len(self.recycleBufContainer); i++ {
		marshal.RecycleMarshalBuf(self.recycleBufContainer[i])
		self.recycleBufContainer[i] = nil
	}
	g_marshalManualRecyclerPool.Push(self)
}

/////////////////////////////////////////
func GetIdSeperateCfg(module string, id int, name string, serverSeperateCfg string) (val string, err error) {
	defer func() {
		if r := recover(); r != nil {
			val = ""
			err = r.(error)
		}
	}()

	defaultVal := ""
	serverVal := ""
	checker := map[int]byte{}
	SplitOnStrListCore(module, serverSeperateCfg, ";", func(str string) bool {
		targetId := -1
		strs := strings.SplitN(str, ":", 2)

		validPartCnt := 0
		for i := 0; i < len(strs); i++ {
			substr := strings.TrimSpace(strs[i])
			if substr == "" {
				continue
			}

			switch i {
			case 0:
				if strings.ToLower(substr) == "default" {
					targetId = 0
				} else {
					var err2 error
					targetId, err2 = strconv.Atoi(substr)
					if err2 != nil {
						panic(fmt.Errorf("execCfg %s id format err: %s", name, err2))
					}
					if targetId <= 0 {
						panic(fmt.Errorf("execCfg %s id(%d) should be > 0", name, targetId))
					}
				}
				validPartCnt++
			case 1:
				if targetId == 0 {
					if defaultVal != "" {
						panic(fmt.Errorf("execCfg %s default cgf should be not more than one", name))
					}
					defaultVal = substr
				} else {
					_, existed := checker[targetId]
					if existed {
						panic(fmt.Errorf("execCfg %s id(%d) cgf is dup", name, targetId))
					}
					if id == targetId {
						serverVal = substr
					}
					checker[targetId] = 1
				}
				validPartCnt++
			}
		}
		if validPartCnt != 2 {
			panic(fmt.Errorf("execCfg %s format err, should be ['default'|id]:val", name))
		}

		return true
	})
	if defaultVal == "" {
		return "", fmt.Errorf("execCfg %s lost 'default:xxx'", name)
	}
	if serverVal == "" {
		return defaultVal, nil
	}
	return serverVal, nil
}

func GetIdSeperateInt32Cfg(module string, id int, name string, serverSeperateCfg string) (val int32, err error) {
	cfgStr, err := GetIdSeperateCfg(module, id, name, serverSeperateCfg)
	if err != nil {
		log.Error(module, "%s load fail, err:%s", name, err)
		return
	}

	val_, err := strconv.ParseInt(cfgStr, 10, 32)
	if err != nil {
		log.Error(module, "%s format err:%s", name, err)
		return
	}

	val = int32(val_)

	log.Info(module, log.I, "current %s is [%d]", name, val)
	return
}

func GetIdSeperateDBAddrCfg(module string, id int, name string, serverSeperateCfg string) (dbAddr string, err error) {
	cfgStr, err := GetIdSeperateCfg(module, id, name, serverSeperateCfg)
	if err != nil {
		log.Error(module, "%s load fail, err:%s", name, err)
		return
	}

	strs := strings.SplitN(cfgStr, ":", 2)
	if len(strs) != 2 {
		return "", fmt.Errorf("execCfg %s, db address format err, should be 'host:port", name)
	}
	_, err = strconv.Atoi(strs[1])
	if err != nil {
		log.Error(module, "execCfg %s, db addr port lost, err:%s", name, err)
		return
	}

	dbAddr = cfgStr

	log.Info(module, log.I, "current %s is [%s]", name, dbAddr)
	return
}

////////////////////////////////////////////

func StartSyncWithMasterBGTask(name string, firstPullInterval time.Duration, maintainPullInterval time.Duration, sendPullReqProc func(), getPullCnt func() int32) {
	if sendPullReqProc == nil {
		panic(fmt.Errorf("%s: StartSyncWithMasterBGTask sendPullReqProc is null", name))
	}
	if getPullCnt == nil {
		panic(fmt.Errorf("%s: StartSyncWithMasterBGTask getPullCnt is null", name))
	}

	go func() {
		defer DeferRecoverPanic(name + "_StartSyncWithMasterBGTask")

		sendPullReqProc()

		firstPullTicker := time.NewTicker(firstPullInterval)
		for {
			<-firstPullTicker.C

			if getPullCnt() > 0 {
				firstPullTicker.Stop()
				break
			}

			sendPullReqProc()
		}

		// 在首次pull成功之后,每隔一段时间就重新pull一下,防止漏接数据
		maintainPullTicker := time.NewTicker(maintainPullInterval)
		for {
			<-maintainPullTicker.C

			sendPullReqProc()
		}
	}()
}

func ZlibEncode(rltBuf *bytes.Buffer, srcBuf []byte) error {
	w := zlib.NewWriter(rltBuf)
	_, err := w.Write(srcBuf)
	if err != nil {
		return err
	}
	w.Close()
	return nil
}

func ZlibDecode(rltBuf *bytes.Buffer, srcBuf []byte) error {
	b := bytes.NewReader(srcBuf)
	r, err := zlib.NewReader(b)
	if err != nil {
		return err
	}
	io.Copy(rltBuf, r)
	r.Close()
	return nil
}

func GetMD5(content []byte) string {
	md5Ctx := md5.New()
	md5Ctx.Write(content)
	return fmt.Sprintf("%x", md5Ctx.Sum(nil))
}

func JsonClone(src interface{}, newContainer interface{}) error {
	buf, err := json.Marshal(src)
	if err == nil {
		return json.Unmarshal(buf, newContainer)
	}
	return err
}

func ZlibMarshal(data marshal.MarshalEntity, typeStr string) (buf []byte, err error) {
	var resBytes bytes.Buffer
	ok := marshal.Marshal("", data, typeStr, func(marshaledBuf []byte) {
		err = ZlibEncode(&resBytes, marshaledBuf)
	})
	if !ok {
		return nil, fmt.Errorf("%s marshal fail", typeStr)
	}
	return resBytes.Bytes(), err
}

func RemoveDupInShortList(elems []interface{}, checkEqual func(a interface{}, b interface{}) bool) (rlt []interface{}) {
	rlt = []interface{}{}
	for i := 0; i < len(elems); i++ {
		isDup := false
		for j := 0; j < i; j++ {
			if checkEqual(elems[i], elems[j]) {
				isDup = true
				break
			}
		}
		if !isDup {
			rlt = append(rlt, elems[i])
		}
	}
	return
}

const (
	iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$"
	iSBN13RegexString = "^(?:(?:97(?:8|9))[0-9]{10})$"
)

var (
	iSBN10Regex = regexp.MustCompile(iSBN10RegexString)
	iSBN13Regex = regexp.MustCompile(iSBN13RegexString)
)

func IsISBN10(s string) bool {
	if !iSBN10Regex.MatchString(s) {
		return false
	}

	var checksum int32
	var i int32

	for i = 0; i < 9; i++ {
		checksum += (i + 1) * int32(s[i]-'0')
	}

	if s[9] == 'X' {
		checksum += 10 * 10
	} else {
		checksum += 10 * int32(s[9]-'0')
	}

	return checksum%11 == 0
}
func IsISBN13(s string) bool {
	if !iSBN13Regex.MatchString(s) {
		return false
	}

	var checksum int32
	var i int32

	factor := []int32{1, 3}

	for i = 0; i < 12; i++ {
		checksum += factor[i%2] * int32(s[i]-'0')
	}

	return (int32(s[12]-'0'))-((10-(checksum%10))%10) == 0
}
func IsISBN(str string) (int64, bool) {
	s := strings.Replace(strings.Replace(str, "-", "", -1), " ", "", -1)
	ok := IsISBN13(s) || IsISBN10(s)
	if !ok {
		return 0, false
	}
	isbnInt, err := strconv.ParseInt(s, 10, 64)
	if err != nil {
		return 0, false
	}
	return isbnInt, true
}

func IsNil(v interface{}) bool {
	if v == nil {
		return true
	}

	return reflect.ValueOf(v).IsNil()
}

func JsonCopy[T any](srcObj *T, dstObj *T) error {
	bs, err := json.Marshal(srcObj)
	if err != nil {
		return err
	}
	return json.Unmarshal(bs, dstObj)
}

var dict = []rune{'H', 'I', 'J', 'K', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'L', 'M', 'N', 'O', '0', '1', '2', '3', '4', '5', '6', '7', 'h', 'i', 'j', 'B', 'C', 'D', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'E', 'F', 'G', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}

func NumToCode(num uint64) string {
	if num == 0 {
		return string([]rune{dict[num]})
	}

	charCnt := uint64(len(dict))

	var str []rune
	for ; num > 0; num = num / charCnt {
		str = append(str, dict[num%charCnt])
	}
	return string(str)
}
